"""
《邢不行-2020新版|Python数字货币量化投资课程》
无需编程基础，助教答疑服务，专属策略网站，一旦加入，永续更新。
课程详细介绍：https://quantclass.cn/crypto/class
邢不行微信: xbx3636
本程序作者: 邢不行

# 课程内容
    币安期现套利
    1. 先将现货中放入usdt，币本位合约中放入若干symbol的币
    2. 获取现货symbol价格，获取币本位合约symbol价格，计算利差
    3. 当利差满足要求，分别计算币本位合约开空的数量及买入现货的数量，
"""
import os
from Function import *
from Market import *


# todo 每次循环，根据期限价差的差值变动，分批下单
# 现货中买入1/10的open_execute_value并转至合约
def buy_symbol_in_advance(symbol, open_execute_value_10, spot_sell1_price, future_buy1_price,
                          spot_price_precision, spot_quantity_precision):
    spot_balance = fetch_symbol_assets_num(symbol, 'spot')
    coin_balance = fetch_symbol_assets_num(symbol, 'coin')
    # 判断币本位账户交易对的余额是否比open_execute_value的1/10还少；少则现货买入(不少于20u)，多则直接转账
    # 当现货和合约中的总币数小于open_execute_value_10 / spot_sell1_price，并且
    _balance = open_execute_value_10 / spot_sell1_price
    # 当合约中的交易对数量比_balance少，但是现货+合约的交易对数量比_balance多，意味着现货中的币没有转到合约中
    if coin_balance < _balance <= (coin_balance + spot_balance):
        # 不需要买币，仅从现货向合约转币
        binance_account_transfer(exchange=exchange, symbol=symbol, amount=float(spot_balance),
                                 from_account='现货', to_account='合约')
        return True
    # 当现货+合约中的交易对数量比_balance中少，则需要现货买入交易对并转入合约中
    elif coin_balance + spot_balance < _balance:
        # 如果1/10的open_execute_value价值少于20u，则买20u的现货交易对，否则买1/10的open_execute_value的交易对
        future_coin_num_1 = ((open_execute_value_10 / future_buy1_price) - spot_balance - coin_balance) \
            if (open_execute_value_10 >= 20) else (20 / spot_sell1_price)

        spot_price_1 = spot_sell1_price * (1 + price_slippage)
        spot_symbol = symbol + 'USDT'  # 现货交易对名称
        binance_spot_place_order(exchange=exchange, spot_symbol=spot_symbol,
                                 long_or_short='买入', price=round(spot_price_1, spot_price_precision),
                                 amount=round(future_coin_num_1, spot_quantity_precision))
        spot_amount_1 = fetch_symbol_assets_num(symbol, 'spot')
        time.sleep(1)
        # 现货向合约转币
        binance_account_transfer(exchange=exchange, symbol=symbol, amount=spot_amount_1,
                                 from_account='现货', to_account='合约')
        # 当合约中的交易对数量比_balance中多，现货中也有改交易对余额时，不转账
        return True
    return False


def open_position(symbol_list):
    # 获取通过期限套利程序，在币本位合约建仓的交易对
    if os.path.isfile('open_position_symbol_list.npy') is False:
        open_position_symbol_list = []
    else:
        open_position_symbol_list = np.load('../data/open_position_symbol_list.npy', allow_pickle=True).item()

    # 获取已保存的现货和合约交易对精度信息
    symbol_precision_info = np.load('../data/symbol_precision_info.npy', allow_pickle=True).item()
    for symbol in symbol_list:

        open_execute_value = fetch_open_execute_value(symbol)  # 计划投入资金

        spot_symbol = symbol + 'T'  # 现货交易对名称(如'BTCUSD'变为'BTCUSDT')
        future_symbol = symbol + future_date  # 合约交易对名称

        # 获取现货交易对精度
        spot_price_precision, spot_quantity_precision = symbol_precision_info[spot_symbol]
        # 获取币本位合约交易对精度
        future_price_precision, future_quantity_precision = symbol_precision_info[future_symbol]

        # 获取期货买一数据。因为期货是卖出，取买一的最优挂单。
        # GET /dapi/v1/ticker/bookTicker
        future_buy1_price = float(exchange.dapiPublicGetTickerBookTicker(
            params={'symbol': future_symbol})[0]['bidPrice'])  # 无需round()，返回的数据自带精度

        # 获取现货卖一数据。因为现货是买入，取卖一的最优挂单。
        # GET /api/v3/ticker/bookTicker
        spot_sell1_price = float(exchange.publicGetTickerBookTicker(
            params={'symbol': spot_symbol})['askPrice'])  # 无需round()，返回的数据自带精度

        open_execute_value_10 = open_execute_value / 10
        # 若现货和合约中，该交易对数量不足open_execute_value / 10对应的交易对数量时，则做额外处理并跳过本次循环
        if buy_symbol_in_advance(symbol, open_execute_value_10, spot_sell1_price, future_buy1_price,
                                 spot_price_precision, spot_quantity_precision):
            print(f"现货中{symbol}的数量为：{fetch_symbol_assets_num(symbol, 'spot'):.3f}")
            print(f"合约中{symbol}的数量为：{fetch_symbol_assets_num(symbol, 'coin'):.3f}")
            continue

        print(f"现货中{symbol}的数量为：{fetch_symbol_assets_num(symbol, 'spot'):.3f}")
        print(f"合约中{symbol}的数量为：{fetch_symbol_assets_num(symbol, 'coin'):.3f}")

        # 计算价差
        r = round(future_buy1_price / spot_sell1_price - 1, 4)
        # %.2f为保留2位小数；%.2f%%为保留2位小数并按%输出
        print(f'现货价格：%.{spot_price_precision}f，期货价格：%.{future_price_precision}f，价差：%.2f%%' %
              (spot_sell1_price, future_buy1_price, r * 100))

        # ===判断价差是否满足要求
        if r < r_open_threshold:
            print('利差小于目标阀值，不入金\n')
        else:
            print('利差大于目标阀值，开始入金：现货买入后，转入币本位合约开空\n')

            # 计算开空合约的数量
            future_contract_num = int(open_execute_value / fetch_contact_size(symbol))  # 开空合约张数
            future_coin_num = open_execute_value / future_buy1_price  # 合约张数对应币的数量
            # 现货需要买入的币的数量
            future_fee = future_coin_num * future_fee_rate  # 合约手续费
            # 公式为：(spot_amount -  future_fee) *  (1 - spot_fee_rate) = future_coin_num
            spot_amount = future_coin_num / (1 - spot_fee_rate) + future_fee

            print(f'交易计划：开空合约张数：%.f；对应币数量：%.3f；需要买入现货数量：%.3f\n' %
                  (future_contract_num, future_coin_num, spot_amount))

            # 现货买币
            spot_price = round(spot_sell1_price * (1 + price_slippage), spot_price_precision)
            spot_buy_info = binance_spot_place_order(exchange=exchange, spot_symbol=spot_symbol,
                                                     long_or_short='买入', price=spot_price,
                                                     amount=round(spot_amount, spot_quantity_precision))
            print(f'现货下单信息：\n{spot_buy_info}')

            # 合约做空
            future_price = round(future_buy1_price * (1 - price_slippage), future_price_precision)
            future_sell_info = binance_future_place_order(exchange=exchange, future_symbol=future_symbol,
                                                          long_or_short='开空', price=future_price,
                                                          amount=future_contract_num)
            print(f'合约下单信息：\n{future_sell_info}')

            time.sleep(1)
            # 获取现货账户买入币的数量
            print(f"现货中{symbol}的币数量为：{fetch_symbol_assets_num(symbol, 'spot')}")
            # 将现货交易所买到的币转到合约账户
            binance_account_transfer(exchange=exchange, symbol=symbol, amount=spot_amount,
                                     from_account='现货',
                                     to_account='合约')
            # 将symbol添加到持仓集合open_position_symbol_list中，并去重
            open_position_symbol_list.append(symbol)
            open_position_symbol_list = list(sorted(set(open_position_symbol_list)))
            # 保存文件
            np.save('../data/open_position_symbol_list.npy', open_position_symbol_list)
            print(f'币本合约已建仓币对集合为：{open_position_symbol_list}')

        # ===循环结束
        print('*' * 20, '本次循环结束，暂停', '*' * 20, '\n')
        time.sleep(2)


if __name__ == '__main__':
    # try:
    # 判断并修改为单向持仓
    # if_oneway_mode()
    # 获取市场所有币本位合约交易对精度
    symbol_precision_info = np.load(r'../data/coin_symbol_precision_info.npy', allow_pickle=True).item()
    while True:
        open_position(symbol_list)
    # except Exception as e:
    #     print('系统出错，10s之后重新运行，出错原因：' + str(e))
    #     print(e)
    #     time.sleep(2)
